using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;

using System.IPod.Music;
using Project.Ime.Properties;
using System.Text.RegularExpressions;
using System.IPod;

namespace Project.Ime
{
	public partial class MainForm : Form
	{
		const string AppName = "iPod Media Extractor 1.1";

		delegate void VoidDelegate();
		delegate void IntDelegate(int value);
		delegate void BoolDelegate(bool value);
		delegate void ButtonBoolDelegate(Button button, bool value);
		delegate void StringDelegate(string value);

		struct StateObject
		{
			public string DestPath;
			public TrackItem[] Items;
		}

		enum SortOrder
		{
			Ascending,
			Descending
		}

		string m_ipodRoot = string.Empty;
		string m_pathToDb = string.Empty;
		ITunesDb m_db;
		BackgroundWorker m_dbWorker;
		BackgroundWorker m_fileCopyWorker;
		TrackItem[] m_currentSongs;
		IComparer m_currentComparer = new SongComparer(true);

		public MainForm()
		{
			InitializeComponent();

			Text = AppName;
			
			// Set system font
			Font = SystemFonts.StatusFont;
			
			InitGui();
		}

		private void HandleFormShown(object sender, EventArgs e)
		{
			InitWorkers();
			if (!RefreshDatabase())
				MessageBox.Show("Unable to find iPod. Make sure it is connected and press F5 to refresh.");
		}

		private void InitGui()
		{
			Size = Settings.Default.WindowSize;
			Location = Settings.Default.WindowLocation;
			m_splitContainer.SplitterDistance = Settings.Default.HorizontalSplitter;

			if (!string.IsNullOrEmpty(Settings.Default.TargetDir))
				m_textBoxTarget.Text = Settings.Default.TargetDir;

			foreach (ColumnHeader column in m_listViewSongs.Columns)
			{
				column.Tag = SortOrder.Descending;
				column.ImageIndex = 2;
			}

			// Artist
			m_listViewSongs.Columns[0].Tag = SortOrder.Ascending;
			m_listViewSongs.Columns[0].ImageIndex = 0;
		}

		private bool FindIpodDrive()
		{
			string rootPath;
			if (!Settings.Default.OverrideDrive)
				rootPath = ITunesDb.GetIPodRoot();
			else
			{
				rootPath = Settings.Default.OverridePath;
				if (!rootPath.EndsWith("\\"))
					rootPath += "\\";
			}
			
			if (rootPath == string.Empty)
				return false;

			m_ipodRoot = rootPath;
			m_pathToDb = rootPath + "iPod_Control\\iTunes\\iTunesDB";
			this.Text =  AppName + " - " + m_pathToDb;

			return true;
		}

		private bool RefreshDatabase()
		{
			if (!FindIpodDrive())
				return false;

			if (!m_dbWorker.IsBusy)
			{
				m_listBoxArtists.Items.Clear();
				m_listBoxAlbums.Items.Clear();
				m_listViewSongs.Items.Clear();
				
				m_dbWorker.RunWorkerAsync();
			}

			return true;
		}

		private void InitWorkers()
		{
			m_dbWorker = new BackgroundWorker();
			m_dbWorker.WorkerSupportsCancellation = false;
			m_dbWorker.WorkerReportsProgress = true;
			m_dbWorker.DoWork += delegate(object sender, DoWorkEventArgs e)
			{
				try
				{
					BackgroundWorker w = sender as BackgroundWorker;
					LoadDb(w);
				}
				catch (Exception ex)
				{
					MessageBox.Show(ex.Message, "iPod Database", MessageBoxButtons.OK,
						MessageBoxIcon.Error);
				}
			};

			m_dbWorker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs e)
			{
				if (e.ProgressPercentage == 0)
				{
					Cursor = Cursors.WaitCursor;
					if (m_statusStrip.InvokeRequired)
						m_statusStrip.Invoke(new StringDelegate(InternalSetStatusLabelText), "Opening database...");
					else
						InternalSetStatusLabelText("Opening database...");
				}
				else if (e.ProgressPercentage > 0)
				{
					if (m_statusStrip.InvokeRequired)
						m_statusStrip.Invoke(new StringDelegate(InternalSetStatusLabelText), "Ready");
					else
						InternalSetStatusLabelText("Ready");

				}
			};

			m_dbWorker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e)
			{
				Cursor = Cursors.Default;
				string[] artist = m_db.GetArtists();
				Array.Sort(artist);
				m_listBoxArtists.Items.AddRange(artist);
				m_listBoxArtists.Items.Insert(0, "All");
				if (m_statusStrip.InvokeRequired)
					m_statusStrip.Invoke(new StringDelegate(InternalSetStatusLabelText), "Ready");
				else
					InternalSetStatusLabelText("Ready");

				// Apply settings
				if (m_listBoxArtists.InvokeRequired)
					m_listBoxArtists.Invoke(new VoidDelegate(InternalSetActiveArtist));
				else
					InternalSetActiveArtist();

				if (m_listBoxAlbums.InvokeRequired)
					m_listBoxAlbums.Invoke(new VoidDelegate(InternalSetActiveAlbum));
				else
					InternalSetActiveAlbum();
			};

			m_fileCopyWorker = new BackgroundWorker();
			m_fileCopyWorker.WorkerSupportsCancellation = false;
			m_fileCopyWorker.WorkerReportsProgress = true;
			m_fileCopyWorker.DoWork += delegate(object sender, DoWorkEventArgs e)
			{
				try
				{
					if (m_buttonExtract.InvokeRequired)
						m_buttonExtract.Invoke(new ButtonBoolDelegate(InternalSetButtonEnabled), m_buttonExtract, false);
					else
						InternalSetButtonEnabled(m_buttonExtract, false);

					if (m_buttonDest.InvokeRequired)
						m_buttonDest.Invoke(new ButtonBoolDelegate(InternalSetButtonEnabled), m_buttonDest, false);
					else
						InternalSetButtonEnabled(m_buttonDest, false);

					if (m_statusStrip.InvokeRequired)
						m_statusStrip.Invoke(new BoolDelegate(InternalSetProgressVisible), true);
					else
						InternalSetProgressVisible(true);

					if (m_statusStrip.InvokeRequired)
						m_statusStrip.Invoke(new StringDelegate(InternalSetStatusLabelText), "Copying files");
					else
						InternalSetStatusLabelText("Copying files");

					BackgroundWorker w = sender as BackgroundWorker;
					CopyFiles(w, (StateObject)e.Argument);
				}
				catch (Exception ex)
				{
					MessageBox.Show(ex.Message, "File Copy", MessageBoxButtons.OK,
						MessageBoxIcon.Error);
				}
			};

			m_fileCopyWorker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs e)
			{
				if (e.ProgressPercentage == 0)
					Cursor = Cursors.WaitCursor;

				if (m_statusStrip.InvokeRequired)
					m_statusStrip.Invoke(new IntDelegate(InternalSetProgress), e.ProgressPercentage);
				else
					InternalSetProgress(e.ProgressPercentage);
			};

			m_fileCopyWorker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e)
			{
				if (m_buttonExtract.InvokeRequired)
					m_buttonExtract.Invoke(new ButtonBoolDelegate(InternalSetButtonEnabled), m_buttonExtract, true);
				else
					InternalSetButtonEnabled(m_buttonExtract, true);

				if (m_buttonDest.InvokeRequired)
					m_buttonDest.Invoke(new ButtonBoolDelegate(InternalSetButtonEnabled), m_buttonDest, true);
				else
					InternalSetButtonEnabled(m_buttonDest, true);

				if (m_statusStrip.InvokeRequired)
					m_statusStrip.Invoke(new StringDelegate(InternalSetStatusLabelText), "Ready");
				else
					InternalSetStatusLabelText("Ready");

				if (m_statusStrip.InvokeRequired)
					m_statusStrip.Invoke(new BoolDelegate(InternalSetProgressVisible), false);
				else
					InternalSetProgressVisible(false);

				Cursor = Cursors.Default;
				
			};
		}

		private void InternalSetButtonEnabled(Button button, bool value)
		{
			button.Enabled = value;
		}

		private void InternalSetActiveArtist()
		{
			if (!string.IsNullOrEmpty(Settings.Default.ActiveArtist))
			{
				foreach (string item in m_listBoxArtists.Items)
				{
					if (item == Settings.Default.ActiveArtist)
					{
						m_listBoxArtists.SelectedItem = item;
						break;
					}
				}
			}
		}

		private void InternalSetActiveAlbum()
		{
			if (!string.IsNullOrEmpty(Settings.Default.ActiveAlbum))
			{
				foreach (string item in m_listBoxAlbums.Items)
				{
					if (item == Settings.Default.ActiveAlbum)
					{
						m_listBoxAlbums.SelectedItem = item;
						break;
					}
				}
			}
		}

		private void InternalSetProgressVisible(bool value)
		{
			m_toolStripProgressBar.Visible = value;
		}

		private void InternalSetProgress(int value)
		{
			m_toolStripProgressBar.Value = value;
		}

		private void InternalSetStatusLabelText(string value)
		{
			m_toolStripStatusLabel.Text = value;
		}

		private void CopyFiles(BackgroundWorker w, StateObject obj)
		{
			int counter = 0;
			w.ReportProgress(counter);
			CopyFormat format;
			
			if (Array.Find<string>(Enum.GetNames(typeof(CopyFormat)), delegate(string s) { return s == Settings.Default.CopyFormat; }) == null)
				format = CopyFormat.Long;
			else
				format = (CopyFormat)Enum.Parse(typeof(CopyFormat), Settings.Default.CopyFormat);
			
			foreach (TrackItem song in obj.Items)
			{
				counter++;
				string sourceFile = m_ipodRoot + song.Location;
				string destFile = string.Empty;
				if (format == CopyFormat.Short)
					destFile = Path.GetFullPath(obj.DestPath) + "\\" + ReplaceIllegalChars(song.Title) + Path.GetExtension(sourceFile);
				else if (format == CopyFormat.Long)
					destFile = Path.GetFullPath(obj.DestPath) + "\\" + ReplaceIllegalChars(song.Artist) + "\\" + ReplaceIllegalChars(song.Album) + "\\" + ReplaceIllegalChars(song.Title) + Path.GetExtension(sourceFile);
				else if (format == CopyFormat.Full)
					destFile = Path.GetFullPath(obj.DestPath) + "\\" + ReplaceIllegalChars(song.Artist) + "\\" + ReplaceIllegalChars(song.Album) + "\\" + FormatTrackNumber(song.TrackNumber) + " " + ReplaceIllegalChars(song.Title) + Path.GetExtension(sourceFile);

				string targetDir = Path.GetDirectoryName(destFile);
				if (!Directory.Exists(targetDir))
					Directory.CreateDirectory(targetDir);

				Debug.WriteLine("Copying " + sourceFile + " to " + destFile);
				int p = (int)(counter / (float)obj.Items.Length * 100.0f);
				w.ReportProgress(p);
				
				File.Copy(sourceFile, destFile, true);
			}
		}

		private string ReplaceIllegalChars(string fileName)
		{
			return Regex.Replace(fileName, "(/)|(:)|(\")|(>)|(<)|(\\*)|(\\?)|(\\x5c)|(\\|)", "_");
		}

		private string FormatTrackNumber(int trackNumber)
		{
			if (trackNumber < 10)
				return "0" + trackNumber;
			else
				return trackNumber.ToString();
		}

		private void LoadDb(BackgroundWorker w)
		{
			w.ReportProgress(0);
			if (m_db == null)
				m_db = new ITunesDb();
			
			m_db.Clear();

			// Find ipod
			string dbFileName = GetIPodDbFileName();

			m_db.Load(dbFileName);
		}

		private string GetIPodDbFileName()
		{
			return m_pathToDb;
		}

		private void HandleRefreshDatabaseClick(object sender, EventArgs e)
		{
			if (!RefreshDatabase())
				MessageBox.Show("Unable to find iPod. Make sure it is connected and press F5 to refresh.");
		}

		private void HandleArtistChanged(object sender, EventArgs e)
		{
			if (m_listBoxArtists.SelectedIndex < 0)
				return;

			string artistName = (string)m_listBoxArtists.Items[m_listBoxArtists.SelectedIndex];
			string[] albums;
			if (artistName == "All")
				albums = m_db.GetAlbums();
			else
				albums = m_db.GetAlbums(artistName);
			Settings.Default.ActiveArtist = artistName;

			Array.Sort(albums);
			m_listBoxAlbums.Items.Clear();
			m_listBoxAlbums.Items.AddRange(albums);
			m_listBoxAlbums.Items.Insert(0, "All");

			m_listBoxAlbums.SelectedIndex = 0;

			EnableDisableExtractButton();
		}

		private void HandleAlbumChanged(object sender, EventArgs e)
		{
			if (m_listBoxAlbums.SelectedIndex < 0 || m_listBoxArtists.SelectedIndex < 0)
				return;

			string artistName = (string)m_listBoxArtists.Items[m_listBoxArtists.SelectedIndex];
			string albumName = (string)m_listBoxAlbums.Items[m_listBoxAlbums.SelectedIndex];
			Settings.Default.ActiveAlbum = albumName;
			if (albumName == "All" && artistName == "All")
				m_currentSongs = m_db.GetSongs();
			else if (albumName == "All" && artistName != "All")
			{
				string[] albums = m_db.GetAlbums(artistName);
				List<TrackItem> albumSongs = new List<TrackItem>();
				foreach (string album in albums)
					albumSongs.AddRange(m_db.GetSongs(album));

				m_currentSongs = albumSongs.ToArray();
			}
			else
				m_currentSongs = m_db.GetSongs(albumName);

			SortSongs();

			m_listViewSongs.VirtualMode = true;
			m_listViewSongs.VirtualListSize = 0;
			m_listViewSongs.VirtualListSize = m_currentSongs.Length;

			EnableDisableExtractButton();
		}

		private void SortSongs()
		{
			Array.Sort(m_currentSongs, m_currentComparer);
		}

		private void HandleExtractClick(object sender, EventArgs e)
		{
			if (string.IsNullOrEmpty(m_textBoxTarget.Text))
			{
				MessageBox.Show("No destination directory selected", "No destination", MessageBoxButtons.OK, MessageBoxIcon.Error);

				return;
			}

			if (!Util.IsPathValid(m_textBoxTarget.Text))
			{
				MessageBox.Show(m_textBoxTarget.Text + " is not a valid path", "Invalid path", MessageBoxButtons.OK, MessageBoxIcon.Error);

				return;
			}

			if (!Directory.Exists(m_textBoxTarget.Text))
				Directory.CreateDirectory(m_textBoxTarget.Text);				
			
			List<TrackItem> items = new List<TrackItem>();
			foreach (int index in m_listViewSongs.SelectedIndices)
			{
				TrackItem item = m_currentSongs[index];
				items.Add(item);
			}

			if (!m_fileCopyWorker.IsBusy)
			{
				StateObject obj = new StateObject();
				obj.DestPath = m_textBoxTarget.Text;
				obj.Items = items.ToArray();
				m_fileCopyWorker.RunWorkerAsync(obj);
			}
		}

		private void HandleAboutClick(object sender, EventArgs e)
		{
			AboutDialog dialog = new AboutDialog();
			dialog.ShowDialog();
		}

		private void HandleExitClick(object sender, EventArgs e)
		{
			Application.Exit();
		}

		private void HandleBrowseClick(object sender, EventArgs e)
		{
			FolderBrowserDialog dialog = new FolderBrowserDialog();
			if (dialog.ShowDialog() == DialogResult.OK)
			{
				m_textBoxTarget.Text = dialog.SelectedPath;
				Settings.Default.TargetDir = dialog.SelectedPath;
			}
		}

		private void HandleSongChanged(object sender, EventArgs e)
		{
			EnableDisableExtractButton();
		}

		private void HandleTextChanged(object sender, EventArgs e)
		{
			Settings.Default.TargetDir = m_textBoxTarget.Text;
		}

		private void HandleClosing(object sender, FormClosingEventArgs e)
		{
			SaveSettings();
		}

		private void SaveSettings()
		{
			Settings.Default.WindowLocation = Location;
			Settings.Default.WindowSize = Size;
			Settings.Default.HorizontalSplitter = m_splitContainer.SplitterDistance;

			Settings.Default.Save();
		}

		private void HandleOptionsClick(object sender, EventArgs e)
		{
			OptionsDialog dialog = new OptionsDialog();
			if (dialog.ShowDialog() == DialogResult.OK)
			{
				Settings.Default.CopyFormat = dialog.CopyFormat;
				Settings.Default.OverrideDrive = dialog.OverrideDrive;
				Settings.Default.OverridePath = dialog.OverridePath;
				Settings.Default.Save();
			}
		}

		private void HandleRetrieveItem(object sender, RetrieveVirtualItemEventArgs e)
		{
			TrackItem song = m_currentSongs[e.ItemIndex];
			ListViewItem item = new ListViewItem(new string[] { song.Title, song.Artist, song.Album, song.TrackNumber + " of " + song.TotalTracks, song.Genre });
			item.Tag = song;
			e.Item = item;
		}

		private void HandleVirtualItemSelectionChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e)
		{
			EnableDisableExtractButton();
		}

		private void EnableDisableExtractButton()
		{
			if (m_listViewSongs.SelectedIndices.Count > 0)
				m_buttonExtract.Enabled = true;
			else
				m_buttonExtract.Enabled = false;
		}

		private void HandleColumnClick(object sender, ColumnClickEventArgs e)
		{
			if (m_currentSongs == null || m_currentSongs.Length <= 0)
				return;

			SortOrder sortOrder = (SortOrder)m_listViewSongs.Columns[e.Column].Tag;
			if (sortOrder == SortOrder.Ascending)
				sortOrder = SortOrder.Descending;
			else
				sortOrder = SortOrder.Ascending;

			m_listViewSongs.Columns[e.Column].Tag = sortOrder;

			for (int i = 0; i < m_listViewSongs.Columns.Count; i++)
			{
				ColumnHeader column = m_listViewSongs.Columns[i];
				if (i == e.Column) // Found clicked column
				{
					if (sortOrder == SortOrder.Ascending)
						column.ImageIndex = 0;
					else
						column.ImageIndex = 1;
				}
				else
					column.ImageIndex = 2;
			}

			if (e.Column == 0)
				m_currentComparer = new SongComparer(sortOrder == SortOrder.Ascending ? true : false);
			else if (e.Column == 1)
				m_currentComparer = new ArtistComparer(sortOrder == SortOrder.Ascending ? true : false);
			else if (e.Column == 2)
				m_currentComparer = new AlbumComparer(sortOrder == SortOrder.Ascending ? true : false);
			else if (e.Column == 3)
				m_currentComparer = new TrackNumberComparer(sortOrder == SortOrder.Ascending ? true : false);
			else if (e.Column == 4)
				m_currentComparer = new GenreComparer(sortOrder == SortOrder.Ascending ? true : false);

			SortSongs();

			m_listViewSongs.VirtualListSize = 0;
			m_listViewSongs.VirtualListSize = m_currentSongs.Length;
		}

		private void HandleSaveClick(object sender, EventArgs e)
		{
			SaveFileDialog dialog = new SaveFileDialog();
			dialog.Filter = "All files (*.*)|*.*";
			if (dialog.ShowDialog() == DialogResult.OK)
			{
				try
				{
					m_db.Save(dialog.FileName);
				}
				catch (DatabaseException ex)
				{
					MessageBox.Show(ex.Message, "Saving database");
				}
			}
		}

		private void HandleAddSongClick(object sender, EventArgs e)
		{
			m_db.AddSong("foo.mp3");
		}
	}

	enum CopyFormat
	{
		Short,
		Long,
		Full
	}

	class SongComparer : IComparer
	{
		bool m_ascending;

		public SongComparer(bool ascending)
		{
			m_ascending = ascending;
		}

		public int Compare(object x, object y)
		{
			TrackItem tx = x as TrackItem;
			TrackItem ty = y as TrackItem;

			int result = StringComparer.CurrentCultureIgnoreCase.Compare(tx.Title, ty.Title);
			if (!m_ascending)
				result *= -1;

			return result;
		}
	}

	class ArtistComparer : IComparer
	{
		bool m_ascending;

		public ArtistComparer(bool ascending)
		{
			m_ascending = ascending;
		}

		public int Compare(object x, object y)
		{
			TrackItem tx = x as TrackItem;
			TrackItem ty = y as TrackItem;

			int result = StringComparer.CurrentCultureIgnoreCase.Compare(tx.Artist, ty.Artist);
			if (!m_ascending)
				result *= -1;

			return result;
		}
	}

	class AlbumComparer : IComparer
	{
		bool m_ascending;

		public AlbumComparer(bool ascending)
		{
			m_ascending = ascending;
		}

		public int Compare(object x, object y)
		{
			TrackItem tx = x as TrackItem;
			TrackItem ty = y as TrackItem;

			int result = StringComparer.CurrentCultureIgnoreCase.Compare(tx.Album, ty.Album);
			if (!m_ascending)
				result *= -1;

			return result;
		}
	}

	class TrackNumberComparer : IComparer
	{
		bool m_ascending;

		public TrackNumberComparer(bool ascending)
		{
			m_ascending = ascending;
		}

		public int Compare(object x, object y)
		{
			TrackItem tx = x as TrackItem;
			TrackItem ty = y as TrackItem;

			int result = 0;
			if (tx.TrackNumber < ty.TrackNumber)
				result = -1;
			else if (tx.TrackNumber > ty.TrackNumber)
				result = 1;
			if (!m_ascending)
				result *= -1;

			return result;
		}
	}

	class GenreComparer : IComparer
	{
		bool m_ascending;

		public GenreComparer(bool ascending)
		{
			m_ascending = ascending;
		}

		public int Compare(object x, object y)
		{
			TrackItem tx = x as TrackItem;
			TrackItem ty = y as TrackItem;

			int result = StringComparer.CurrentCultureIgnoreCase.Compare(tx.Genre, ty.Genre);
			if (!m_ascending)
				result *= -1;

			return result;
		}
	}
}